#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Globalization;

namespace Grawlix.Geometry
{
	/// <summary>
	/// An immutable 2D transform representing rotation and
	/// translation.
	/// </summary>
	public struct Transform2 : IEquatable<Transform2>
	{
		/// <summary>
		/// The identity transformation.
		/// </summary>
		public static readonly Transform2 Identity =
			new Transform2(Vector2.Zero, Rotation2.Identity);

		/// <summary>
		/// The zero transformation.
		/// </summary>
		public static readonly Transform2 Zero =
			new Transform2(Vector2.Zero, Rotation2.Zero);

		/// <summary>
		/// The displacement of this transform.
		/// </summary>
		private readonly Vector2 _displacement;

		/// <summary>
		/// The rotation matrix of this transform.
		/// </summary>
		private readonly Rotation2 _rotation;

		/// <summary>
		/// Initializes a new transform with displacement and rotation.
		/// </summary>
		/// <param name="displacement">The offset to change all
		/// position vectors by.</param>
		/// <param name="rotation">The rotation matrix to apply to
		/// vectors.
		/// </param>
		public Transform2(Vector2 displacement, Rotation2 rotation)
		{
			_displacement = displacement;
			_rotation = rotation;
		}

		/// <summary>
		/// The displacement of this transform.
		/// </summary>
		public Vector2 Displacement
		{
			get { return _displacement; }
		}

		/// <summary>
		/// The rotation matrix of this transform.
		/// </summary>
		public Rotation2 Rotation
		{
			get { return _rotation; }
		}

		/// <summary>
		/// Applies the transformation to a position vector.
		/// </summary>
		/// <param name="vector">The vector to apply to.</param>
		/// <returns>The transformed vector.</returns>
		/// <remarks>To apply to a displacement vector, multiply by
		/// rotation.</remarks>
		public Vector2 Apply(Vector2 vector)
		{
			return _rotation * vector + _displacement;
		}

		/// <summary>
		/// Applies the inverse transformation to a position vector.
		/// </summary>
		/// <param name="vector">The vector to apply to.</param>
		/// <returns>The transformed vector.</returns>
		/// <remarks>To apply to a displacement vector, multiply by
		/// inverse rotation.</remarks>
		public Vector2 ApplyInverse(Vector2 vector)
		{
			return _rotation.Invert() * (vector - _displacement);
		}

		/// <summary>
		/// Indicates whether this instance and a specified object are
		/// equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj" /> and this instance are the
		/// same type and represent the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to. </param>
		public override bool Equals(object obj)
		{
			return obj.GetType() == typeof(Transform2) &&
			       Equals((Transform2)obj);
		}

		/// <summary>
		/// Indicates whether the current object is equal to another
		/// object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the
		/// <paramref name="other" /> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.
		/// </param>
		public bool Equals(Transform2 other)
		{
			return other._rotation.Equals(_rotation) &&
			       other._displacement.Equals(_displacement);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this
		/// instance.
		/// </returns>
		public override int GetHashCode()
		{
			unchecked
			{
				return (_rotation.GetHashCode() * 397) ^
				       _displacement.GetHashCode();
			}
		}

		/// <summary>
		/// Finds the inverse of this transformation.
		/// </summary>
		/// <returns>The inverse of the current transformation.</returns>
		public Transform2 Invert()
		{
			Rotation2 inverse = _rotation.Invert();
			return new Transform2(inverse * -_displacement, inverse);
		}

		/// <summary>
		/// Obtains a string representation of this object.
		/// </summary>
		/// <returns>A string representation of this object.</returns>
		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture,
			                     "Rotation: {0}, Displacement: {1}",
			                     _rotation, _displacement);
		}

		/// <summary>
		/// Determines whether two <see cref="Transform2"/> objects
		/// have the same value.
		/// </summary>
		/// <param name="left">A <see cref="Transform2"/>.</param>
		/// <param name="right">A <see cref="Transform2"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is
		/// the same as <paramref name="right"/>; else false.</returns>
		public static bool operator ==(
			Transform2 left, Transform2 right)
		{
			return left.Equals(right);
		}

		/// <summary>
		/// Determines whether two <see cref="Transform2"/> objects
		/// have different values.
		/// </summary>
		/// <param name="left">A <see cref="Transform2"/>.</param>
		/// <param name="right">A <see cref="Transform2"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is
		/// different from <paramref name="right"/>; else false.
		/// </returns>
		public static bool operator !=(
			Transform2 left, Transform2 right)
		{
			return !left.Equals(right);
		}
	}
}